package com.ruoyi.web.controller.inversion;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.CalcThreadManager;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ProcessStopException;
import com.ruoyi.common.utils.ExecUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SseEmitterUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.CommonUtil;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.dm.domain.po.GpsTargetStructPO;
import com.ruoyi.dm.domain.po.SpaceTargetMeasurePO;
import com.ruoyi.dm.mapper.DataManagementMapper;
import com.ruoyi.dm.service.ISpaceTargetMeasureService;
import com.ruoyi.dm.service.impl.GpsTargetInfoImpl;
import com.ruoyi.dm.service.impl.GpsTargetInfoStructService;
import com.ruoyi.web.core.config.FileMonitorUtil;
import com.ruoyi.web.vo.InversionGpsVo;
import common.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import plugins.LowAtmosphereCalcNative;

import java.io.File;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * <p>
 *  大气密度反演 - TLE反演
 * </p>
 *
 * @author liuhl
 * @since 2024-07-15
 */
@RestController
@RequestMapping("/inversion/gps")
@AllArgsConstructor
@Slf4j
@Api(tags = "大气密度反演 - GPS反演")
public class InversionGPSController {

    private DataManagementMapper dataManagementMapper;
    private GpsTargetInfoStructService infoStructService;
    private GpsTargetInfoImpl gpsTargetInfoService;
    private ISpaceTargetMeasureService measureService;
    //private DruidDataSource dataSource;

    private static final Object LOCK = new Object();


    @PostMapping("/invert")
    @ApiOperation(value = "密度反演", notes = "根据时间范围、卫星、模型得到反演结果", httpMethod = "POST")
    public AjaxResult tleInversion(@RequestBody InversionGpsVo vo) throws Exception {


        String sseId = vo.getSseId()+SecurityUtils.getLoginUser().getUsername();
        vo.setSseId(sseId);

        FutureTask<AjaxResult> task = new FutureTask<>(() ->{
            AjaxResult result =     invertInner(vo);
            return result;
        });
        CalcThreadManager.addThread(sseId,task);
        try {
            new Thread(task).start();
            return task.get();
        } catch (InterruptedException | CancellationException exception) {
            throw new ProcessStopException();
        } catch (ExecutionException e) {
            if (e.getCause().getClass() == ProcessStopException.class) {
                throw new ProcessStopException();
            }
            throw new Exception(e);
        }
    }


    /**
     * 实际 执行密度反演
     * @param vo
     * @return
     * @throws SQLException
     */
    public AjaxResult invertInner(@RequestBody InversionGpsVo vo) throws SQLException {

        //TLE和GPS密度反演的结果展示部分需要修改一下，@程雪  @韩佩  @刘欢乐  @刘江凯  大家先看看修改内容，14:30左右16楼当面讨论下。
        //1.GPS密度反演模块，在密度反演的时候不需要选择模型，去掉大气模型的选项。【TLE密度反演有相同改动】
        //
        //-- GPS反演
        //入参：
        //
        //	前端：页面去掉大气模型的选项。
        //	Java：传参去除 大气模型 参数。
        //	算法：没问题。重新给java一个依赖jar
        //
        //结果：
        //
        //	算法：有改动。
        //
        //
        //-- TLE反演
        //入参：
        //
        //	前端：页面去掉大气模型的选项。
        //	Java：传参去除 大气模型 参数。
        //	算法：没问题。
        //
        //结果：
        //
        //	算法：有改动。
        //
        //2.GPS密度反演模块，在密度反演结束后，展示密度反演结果的时候，大气密度轨道分布图上，需要同时展示 反演结果 和 测量结果（两种）。【TLE密度反演有相同改动】
        //
        //
        //-- GPS反演
        //现状：反演结果已经再页面上画出。
        //改动：新增测量结果：StAtmosphereDensityData 增加一个真值密度变量作为测量结果， 测量结果是Y，epoch是X。
        //
        //
        //--TLE反演
        //现状：反演结果已经页面上画出 当地密度 和 有效密度。
        //改动：去掉有效密度，新增真值密度，数据在 density_acc.txt, 包含时间和值，与density.txt的时间不一致，【【【【待定Pending！！！】】】】】
        //
        //
        //3.GPS密度反演模块，在误差分析的时候，需要同时展示反演密度结果和 四种模型，以及真值，的误差结果，“相对误差”标题保持不变，measure保持不变，model扩展为四种模型，相当于支持五条曲线的显示，其中，真值可能为空。“均方根误差”修改为“log(RE)误差”，同样支持五条曲线的显示。【TLE密度反演有相同改动】
        //
        //
        //-- GPS反演
        //
        //- GPS相对误差
        //现状：画了measure和model两条线。
        //改动：去掉model，新增 4条线，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00"，数据来源于 StAtmosphereDensityData 中的 diff_【model名字】_rel。
        //
        //- GPS均方根误差
        //前端：名字修改为 log(RE)误差 。
        //改动：去掉model，新增 4条线，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00"，数据来源于 StAtmosphereDensityData 中的 diff_【model名字】_logre。
        //
        //-- TLE反演
        //
        //- TLE反演相对误差
        //
        //error_loc_acc.txt  //实测的折线图
        //error_loc_【模型名字】.txt  //4个模型的折线图
        //
        //改动：画折线图，按卫星切换，每个卫星包含实测和4个模型共5条折线，折线的X：txt里面第一列的时间，折线的Y：txt里面的第5列。【【【【待定Pending！！！】】】】】
        //
        //
        //{
        //	"20608": {
        //		"tle": {
        //			"x":[],
        //			"y":[]
        //		},
        //		"acc": {
        //			"x":[],
        //			"y":[]
        //		} 或者  空字符串 ""
        //	},
        //	"15589": {
        //		"tle": {
        //			"x":[],
        //			"y":[]
        //		},
        //		"acc": {
        //			"x":[],
        //			"y":[]
        //		}
        //	},
        //
        //}
        //
        //
        //- TLE反演均方根误差
        //
        //logre_loc_acc.txt  //实测的折线图
        //logre_loc_【模型名字】.txt  //4个模型的折线图
        //
        //改名：名字修改为 log(RE)误差 。
        //改动：画折线图，按卫星切换，每个卫星包含实测和4个模型共5条折线，折线的X：txt里面第一列的时间，折线的Y：txt里面的第5列。【【【【待定Pending！！！】】】】】
        //
        //
        //
        //4.误差统计表 修改一下，比较基准分别为  "实测"，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00" （与当前界面选项显示内容一致）。 统计结果显示，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差。【TLE密度反演有相同改动】
        //
        //- GPS大气密度反演误差统计表
        //
        //表头更改为：卫星名称 比较基准 相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差  共计6列。
        //取值：把代码给刘博士更改。
        //
        //- TLE大气密度反演误差统计表
        //
        //表头改成：目标编码，密度形式，比较基准，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差 。
        //
        //error_stats.txt 内容如下：
        //
        //密度形式，比较基准，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差 。
        //loc,acc,-,0.2557,0.66845,0.003645
        //loc,DTM2000,0.225447,0.2557,0.66845,0.003645
        //loc,HarrisPriester,...
        //loc,JB2008,...
        //loc,NRLMSISE00,...
        //eff,acc,-,0.2557,0.66845,0.003645
        //eff,DTM2000,0.225447,0.2557,0.66845,0.003645
        //eff,HarrisPriester,...
        //eff,JB2008,...
        //eff,NRLMSISE00,...
        //
        //loc【当地密度】eff【有效密度】
        //
        //算法输出的数值的有效位数谁来处理【【【【待定Pending！！！】】】】】
        //
        //
        //5. TLE反演筛选功能
        //
        //-- 给当前的目标筛选(更改为自动选取) 增加一个互斥的手动选取，手动选取是：根据时间范围、模糊关键字(NORAD编号tleCode) 查询库中已有的卫星TLE数据，每个卫星TLE数据给两行根数就足够。
        //-- 供用户选取的卫星列表页面，表头【code吗】变更为【NORAD 编号】，增加一列高度，高度来源于筛选的目标卫星结果txt和参考卫星结果txt，txt内容修改如下：
        //-- 手动选取的没有高度。
        //
        //
        //00900 //卫星编号
        //高度1 //30  单位是km，前端在表头展示 km
        //1 00900U 64063C   22002.32277604  .00000336  00000-0  34919-3 0  9996
        //2 00900  90.1675  37.9183 0028479 158.2714 310.9985 13.73652174847854
        //01510
        //高度2
        //1 01510U 65065C   22002.59234291  .00001196  00000-0  86429-3 0  9999
        //2 01510  90.0341  16.5587 0037481 281.5382  78.1588 13.95834206805179


        String startTime = vo.getStartTime() + " 00:00:00";
        String endTime = vo.getEndTime() + " 23:59:59";
        String sateName = vo.getSateName();
        String sseId = vo.getSseId();

        //自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 1,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        //获取模式名
        String schemaName ="piesat_dgdq" ;
        try {
            //1.准备输入参数
            //1.1 卫星轨道数据
            String tableName = schemaName+".COLLECT_GNSS_" + sateName;
            List<StOrbitPoint> satOrbitDataLst = dataManagementMapper.getGNSSData(tableName, startTime, endTime)
                    .stream()
                    .map(map -> {
                        return new StOrbitPoint( //String epoch, double x, double y, double z, double vx, double vy, double vz
                                // POSITION_X,POSITION_Y,POSITION_Z,SPEED_VX,SPEED_VY,SPEED_VZ
                                DateUtil.formatDateTime((Date) map.get("obs_time")),
                                Double.parseDouble(map.get("position_x").toString()),
                                Double.parseDouble(map.get("position_y").toString()),
                                Double.parseDouble(map.get("position_z").toString()),
                                Double.parseDouble(map.get("speed_vx").toString()),
                                Double.parseDouble(map.get("speed_vy").toString()),
                                Double.parseDouble(map.get("speed_vz").toString())
                        );
                    }).collect(Collectors.toList());
            if (CollUtil.isEmpty(satOrbitDataLst)){
                return AjaxResult.error(startTime+" - "+endTime+" 未查询到卫星轨道数据" );
            }


            //1.2 卫星姿态四元数数据
            tableName = schemaName+".COLLECT_ATTI_" + sateName;
            List<SensorPar.StQuaternion> quaternionDataLst = dataManagementMapper.getATTIData(tableName, startTime, endTime)
                    .stream()
                    .map(map -> {
                        return new SensorPar.StQuaternion( //String epoch, double qx, double qy, double qz, double qs
                                //"EPOCH_TIME" TIMESTAMP(6),
                                //"QX" DECIMAL,
                                //"QY" DECIMAL,
                                //"QZ" DECIMAL,
                                //"QS" DECIMAL,
                                DateUtil.formatDateTime((Date) map.get("epoch_time")),
                                Double.parseDouble(map.get("qx").toString()),
                                Double.parseDouble(map.get("qy").toString()),
                                Double.parseDouble(map.get("qz").toString()),
                                Double.parseDouble(map.get("qs").toString())
                        );
                    }).collect(Collectors.toList());
            if (CollUtil.isEmpty(quaternionDataLst)){
                return AjaxResult.error(startTime+" - "+endTime+" 未查询到卫星姿态四元数据");
            }

            //1.3 卫星本体模型数据
            List<StModelData> satelliteModelDataLst = infoStructService.list(new LambdaQueryWrapper<GpsTargetStructPO>()
                            .eq(GpsTargetStructPO::getMasterCode, sateName)
                    ).stream()
                    .map(po -> {
                        StModelData stModelData = new StModelData();
                        stModelData.panelArea = Double.parseDouble(po.getArea());
                        stModelData.normalVectorX = Double.parseDouble(po.getPositionX());
                        stModelData.normalVectorY = Double.parseDouble(po.getPositionY());
                        stModelData.normalVectorZ = Double.parseDouble(po.getPositionZ());
                        return stModelData;
                    }).collect(Collectors.toList());
            if (CollUtil.isEmpty(satelliteModelDataLst)){
                return AjaxResult.error(startTime+" - "+endTime+" 未查询到卫星本体模型数据");
            }

            //1.4 参考大气密度

            if (sateName.toUpperCase().startsWith("GRACEFO")){
                tableName = schemaName+".SWDATA_OBS_DEN_GRACEFO";
            }else {
                tableName = schemaName+".SWDATA_OBS_DEN_" + sateName;
            }
            // update @ 2025-05-28  修改
//            List<StSimplyDens> refdenslist = new ArrayList<>();
//            try {
//                List<StSimplyDens> densityDataList = dataManagementMapper.getDensityData(tableName, startTime, endTime)
//                        .stream()
//                        .map(map -> {
//                            StSimplyDens stAtmosphereDensityData = new StSimplyDens();
//                            stAtmosphereDensityData.epoch = DateUtil.formatDateTime((Date) map.get("time"));
//                            stAtmosphereDensityData.dens = Double.parseDouble(map.get("density").toString());
//                            return stAtmosphereDensityData;
//                        }).collect(Collectors.toList());
//                if (CollUtil.isNotEmpty(densityDataList)){
//                    refdenslist = densityDataList;
//                }
//            }catch (Exception e){
//                e.printStackTrace();
//                log.error("查询表名:{} 出错，异常信息:{}",tableName, e.getMessage());
//            }
            List<StAtmosphereDensityData> refdenslist = new ArrayList<>();
            try {
                List<StAtmosphereDensityData> densityDataList = dataManagementMapper.getDensityData(tableName, startTime, endTime)
                        .stream()
                        .map(map -> {
                            StAtmosphereDensityData stAtmosphereDensityData = new StAtmosphereDensityData();
                            stAtmosphereDensityData.alt = Double.parseDouble(map.get("alt").toString());
                            stAtmosphereDensityData.lat = Double.parseDouble(map.get("lat").toString());
                            stAtmosphereDensityData.lon = Double.parseDouble(map.get("lon").toString());
                            stAtmosphereDensityData.epoch = DateUtil.formatDateTime((Date) map.get("time"));
                            stAtmosphereDensityData.density = Double.parseDouble(map.get("density").toString());
                            return stAtmosphereDensityData;
                        }).collect(Collectors.toList());
                if (CollUtil.isNotEmpty(densityDataList)){
                    refdenslist = densityDataList;
                }
            }catch (Exception e){
                e.printStackTrace();
                log.error("查询表名:{} 出错，异常信息:{}",tableName, e.getMessage());
            }

            if (CollUtil.isEmpty(refdenslist)){
                return AjaxResult.error(startTime+" - "+endTime+" 未查询到参考大气密度数据");
            }

            //1.6 卫星质量
//            GpsTargetInfoPO po = gpsTargetInfoService.getOne(new LambdaQueryWrapper<GpsTargetInfoPO>().eq(GpsTargetInfoPO::getName, sateName));

//            if (po==null || StrUtil.isBlank(po.getQuality())){
//                return AjaxResult.error(startTime+" - "+endTime+" 未查询到卫星质量数据");
//            }
//            double dSatelliteMass = Double.parseDouble(po.getQuality());

            //1.6.输入数据： 面质比 WIND_AREA / DENS
            List<SpaceTargetMeasurePO> measurePOList = measureService.list(new LambdaQueryWrapper<SpaceTargetMeasurePO>()
                    .eq(SpaceTargetMeasurePO::getName, sateName)
            );
            if (CollUtil.isEmpty(measurePOList)){
                return AjaxResult.error("没有对应本体模型数据，请先新增目标本体模型数据");
            }
            SpaceTargetMeasurePO spaceTargetMeasurePO = measurePOList.get(0);
            StAttributesForceModel attributesForceModel = new StAttributesForceModel();
            attributesForceModel.dDragCd = Double.parseDouble(spaceTargetMeasurePO.getFIndex());
            attributesForceModel.dDragArea = Double.parseDouble(spaceTargetMeasurePO.getWindArea());
            attributesForceModel.dDragMass = Double.parseDouble(spaceTargetMeasurePO.getDens());

            //2. 异步监控 log文件，实时给前端推送日志
            FileMonitorUtil fileMonitorUtil = new FileMonitorUtil();
            String baseDir = PathUtil.tmpModuleDir(BizModule.DENSITY_INVERT)
                    + "/inversion_gps_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_FORMAT);
            System.out.println("baseDir地址为：" + baseDir);
            String logFilePath = baseDir + "/log_" + IdUtil.simpleUUID() + ".log";
            String fileEndStr = "end_of_file###end_of_file";

            CompletableFuture.runAsync(() -> {
                fileMonitorUtil.monitoringFileModifyAndSendMsg(logFilePath, null, fileEndStr, sseId);
            });
            fileMonitorUtil.await();

            //3. 调用得到结果
            List<StPSODensityData> outDataList = new ArrayList<>();//输出数据之大气密度反演数据
            StStaticsData outDataStatics = new StStaticsData();//输出数据之大气密度反演结果统计。

            //卫星轨道数据
            //卫星名称
            //卫星面质比
            //日志文件
            //参考大气密度
            //结果误差
            //误差统计
            // List<StAtmosphereDensityData> var5, List<StPSODensityData> var6, StStaticsData var7
            log.info("ccccc");
            log.info("mmmmm");
            // 增加同步代码块，保证线程安全
            synchronized (LOCK) {
                // 伪逻辑，增加startlink星，求取平均大气密度值
                if (sateName.contains("startlink")) {
                    LowAtmosphereCalcNative.CalcAverageDensityForStarLinkByHpod(satOrbitDataLst, sateName, attributesForceModel, logFilePath, refdenslist);
                }
                LowAtmosphereCalcNative.CalcMutiModelAtmosphereDensityByHpod(satOrbitDataLst, sateName, attributesForceModel, logFilePath, refdenslist, outDataList, outDataStatics);
            }
            ////测试使用半长轴衰减方法计算大气密度值的接口 CalcAtmosphereDensityByDecrementOfSemi
//            LowAtmosphereCalcNative.CalcAtmosphereDensityByDecrementOfSemi(
//                    satOrbitDataLst,
//                    quaternionDataLst,
//                    satelliteModelDataLst,
//                    sateName,
//                    dSatelliteMass,
//                    logFilePath,
//                    refdenslist,
//                    outDataList,
//                    outDataStatics);

            //4. outDataList 存储成文件 -> 调用程雪画图
            if (CollUtil.isEmpty(outDataList)){
                return AjaxResult.error("调用计算结果，返回为空");
            }
            Map<String, List<StPSODensityData>> groupMap = outDataList.stream()
                    .collect(HashMap<String, List<StPSODensityData>>::new, (map, entity) -> {

                        for (int i = 1; i < entity.density_ref.length; i++){
                            if (entity.density_ref[i] == 0){
                                continue;
                            }
                            String key = entity.epoch.substring(0, 10); // 2006-10-01
                            switch (i){
                                case 1:
                                    key = "DTM2020_" + key;
                                    break;
                                case 2:
                                    key = "HARRISPRIESTER_" + key;
                                    break;
                                case 3:
                                    key = "JB2008_" + key;
                                    break;
                                case 4:
                                    key = "NRLMSISE00_" + key;
                                    break;
                            }

                            List<StPSODensityData> list = map.get(key);
                            if (list == null) {
                                list = new ArrayList<>();
                                list.add(entity);
                                map.put(key, list);
                            } else {
                                list.add(entity);
                            }
                        }

                    }, HashMap::putAll);//串行流的第三个combiner不会执行


            String txtDelimiter = ",";
            String txtTitle = "Time,effectAtmosphereResultLon,effectAtmosphereResultLat,effectAtmosphereResultH,effectAtmosphereResultDensityData \n";
            Map<String,String> dayAndPicMap = new ConcurrentHashMap<>();
            //isStop 表示进程是否终止
            AtomicBoolean isStop = new AtomicBoolean(false);
            CompletableFuture.allOf(groupMap
                    .entrySet()
                    .stream()
                    .map(entry -> {
                        return CompletableFuture.runAsync(() -> {

                            String day = entry.getKey();
                            String txtBody = entry.getValue()
                                    .stream()
                                    .sorted(Comparator.comparing(t -> t.epoch))
                                    .map(entity -> {
                                        String[] split = day.split("_");
                                        int i = 0;
                                        switch (split[0]){
                                            case "DTM2020":
                                                i = 1;
                                                break;
                                            case "HARRISPRIESTER":
                                                i = 2;
                                                break;
                                            case "JB2008":
                                                i = 3;
                                                break;
                                            case "NRLMSISE00":
                                                i = 4;
                                                break;
                                            default:
                                                log.error("匹配失败了");
                                                break;
                                        }
                                        return entity.epoch + txtDelimiter
                                                + entity.lon + txtDelimiter
                                                + entity.lat + txtDelimiter
                                                + entity.alt + txtDelimiter
                                                + entity.density_ref[i];
                                    })
                                    .filter(o -> !o.contains("NaN"))
                                    .filter(o -> !o.contains("nan"))
                                    .collect(Collectors.joining("\n"));
                            String txtContent = txtTitle + txtBody;
                            String densityPath = baseDir + "/density_" + day + ".txt";
                            FileWriter.create(new File(densityPath)).write(txtContent);

                            //python new_dataFusion.py SWDATA_OBS_DEN_GRACEB_XXXX.txt /dgdq/tmp/dataFusion/newtest 2005-01-02
                            String codePath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("get_pic_path");
                            List<String> commands = ListUtil.of("python", codePath, densityPath, baseDir, day);
                            String result = null;
                            try {
                                result = ExecUtil.execPythonNoTemplate(commands,sseId);
                            } catch (ProcessStopException e) {
                                pool.shutdownNow();
                                isStop.set(false);
                            }
                            if (result != null && result.contains("###")) {
                                String picPath = result.split("###")[1].trim();
                                dayAndPicMap.put(day, picPath);
                            }

                        }, pool);
                    }).toArray(CompletableFuture[]::new)).join();


            if (isStop.get()) {
                throw new ProcessStopException("动态修正系数计算评估已终止");
            }
            Map<String,Object> resultMap = new HashMap<>();
            //5. 组装数据给前端

            //5.1 图片按日期升序返回
            TreeMap<String, String> sortedPicMap = MapUtil.sort(dayAndPicMap);
            resultMap.put("pic", sortedPicMap);

            //5.2 密度数据
            List<String> timeList = outDataList.stream().map(o -> o.epoch).collect(Collectors.toList());
            List<String> accDensityList = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density_ref[0])).collect(Collectors.toList());
            List<String> densityDTM2020 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density_ref[1])).collect(Collectors.toList());
            List<String> densityHARRISPRIESTER = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density_ref[2])).collect(Collectors.toList());
            List<String> densityJB2008 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density_ref[3])).collect(Collectors.toList());
            List<String> densityNRLMSISE00 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density_ref[4])).collect(Collectors.toList());
            Map<String, List<String>> densityMap = MapUtil.builder(new HashMap<String, List<String>>())
                    .put("x", timeList)
                    .put("DTM2020", densityDTM2020)
                    .put("HARRISPRIESTER", densityHARRISPRIESTER)
                    .put("JB2008", densityJB2008)
                    .put("NRLMSISE00", densityNRLMSISE00)
                    .put("acc_dens", accDensityList)
                    .build();
            resultMap.put("chart", densityMap);

            //5.3和4是 显示 StAtmosphereDensityData 里新增的4个误差，时间是横坐标。
            //3是基于模型或者实测的 相对误差
            //4是基于模型或者实测的 绝对误差 "DTM2020", "HARRISPRIESTER", "JB2008", "NRLMSISE00"
            Map<String,Object> relativeMap = new HashMap<>();
            List<String> relativeAcc = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_rel[0])).collect(Collectors.toList());
            List<String> relativeDTM2020 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_rel[1])).collect(Collectors.toList());
            List<String> relativeHARRISPRIESTER = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_rel[2])).collect(Collectors.toList());
            List<String> relativeJB2008 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_rel[3])).collect(Collectors.toList());
            List<String> relativeNRLMSISE00 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_rel[4])).collect(Collectors.toList());

            relativeMap.put("x", timeList);
//            relativeMap.put("acc", relativeAcc);
            relativeMap.put("DTM2020", relativeDTM2020);
            relativeMap.put("HARRISPRIESTER", relativeHARRISPRIESTER);
            relativeMap.put("JB2008", relativeJB2008);
            relativeMap.put("NRLMSISE00", relativeNRLMSISE00);
            resultMap.put("relative", relativeMap);


            Map<String,Object> absoluteMap = new HashMap<>();
            List<String>  absoluteAcc = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_logRE[0])).collect(Collectors.toList());
            List<String>  absoluteDTM2020 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_logRE[1])).collect(Collectors.toList());
            List<String>  absoluteHARRISPRIESTER = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_logRE[2])).collect(Collectors.toList());
            List<String>  absoluteJB2008 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_logRE[3])).collect(Collectors.toList());
            List<String>  absoluteNRLMSISE00 = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.diff_logRE[4])).collect(Collectors.toList());

            absoluteMap.put("x", timeList);
//            absoluteMap.put("acc", absoluteAcc);
            absoluteMap.put("DTM2020", absoluteDTM2020);
            absoluteMap.put("HARRISPRIESTER", absoluteHARRISPRIESTER);
            absoluteMap.put("JB2008", absoluteJB2008);
            absoluteMap.put("NRLMSISE00", absoluteNRLMSISE00);
            resultMap.put("absolute", absoluteMap);

            //5.5 table
            List<Map<String, Object>> tableData = assembleTableData(sateName,outDataStatics);
            tableData.remove(0);
            resultMap.put("table", tableData);

            //准备结果保存
            String densityTxtContentTitle = "Time(UTC),  Altitude(m),  Geodetic LON(degree),  Geodetic LAT(degree),  Density(kg/m3) \n";
            List<String> savePathDensitys = new ArrayList<>();
            for (int i = 1; i < outDataList.get(0).density_ref.length; i++){
                AtomicInteger atomicInteger = new AtomicInteger(i);
                String densityTxtContentBody = outDataList
                        .stream()
                        .sorted(Comparator.comparing(t -> t.epoch))
                        .map(entity -> {
                            return entity.epoch + txtDelimiter
                                    + entity.alt + txtDelimiter
                                    + entity.lon + txtDelimiter
                                    + entity.lat + txtDelimiter
                                    + entity.density_ref[atomicInteger.get()];
                        })
                        .collect(Collectors.joining("\n"));
                String txtContent = densityTxtContentTitle + densityTxtContentBody;
                String key = "";
                switch (i){
                    case 1:
                        key = "DTM2020_";
                        break;
                    case 2:
                        key = "HARRISPRIESTER_";
                        break;
                    case 3:
                        key = "JB2008_";
                        break;
                    case 4:
                        key = "NRLMSISE00_";
                        break;
                }
                String savePathDensity = baseDir + File.separator + key + "density_gps.txt";
                FileWriter.create(new File(savePathDensity)).write(txtContent);
                savePathDensitys.add(savePathDensity);
            }
            resultMap.put("resultData", JSONUtil.toJsonStr(savePathDensitys));
            resultMap.put("module", ResultSaveModule.DENSITY_INVERT);
            resultMap.put("submodule", ResultSaveModule.DENSITY_INVERT_GPS);
            return AjaxResult.success(resultMap);
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }finally {
            pool.shutdown();
            //600 终止标志
            SseEmitterUtils.sendMessage(sseId, "600");
            SseEmitterUtils.remove(sseId);
        }

    }

    private List<Map<String,Object>> assembleTableData(String sateName,StStaticsData stats){

        List<String> models = CollUtil.newArrayList("acc" ,"DTM2020", "HARRISPRIESTER", "JB2008", "NRLMSISE00");
        return IntStream.range(0, models.size())
                .mapToObj(index -> {
                    return MapUtil.builder(new HashMap<String,Object>())
                            .put("sateName", sateName)      //卫星名称
                            .put("compare", models.get(index)) //比较基准
                            .put("rel_avg", CommonUtil.doubleToScientific(stats.mean_diff_rel[index]) )//相对误差均值，
                            .put("rel_standard", CommonUtil.doubleToScientific(stats.std_diff_rel[index]) )//相对误差标准差，
                            .put("logre_avg", CommonUtil.doubleToScientific(stats.mean_diff_logRE[index]) )//log(RE) 误差均值，
                            .put("logre_standard", CommonUtil.doubleToScientific(stats.std_diff_logRE[index]) )//log(RE) 误差标准差
                            .build();
                }).collect(Collectors.toList());
    }

    @ApiOperation(value = "建立连接", notes = "建立连接")
    @GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter push(String id, String flag) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return SseEmitterUtils.connect(id+loginUser.getUsername(), flag);
    }




}
